#include "player.h"

#include "map.h"


/**
 * 09/03/12 - Steven Hartin - Original Creation
 */

CPlayer::CPlayer(float fX, float fY, float fZ, ENTITY_ID nPlayerId) :
    //m_Score(0),
#ifdef _DEBUG
    m_fSpeed(8.0f),
#else
    m_fSpeed(1.4f),
#endif
    m_nID(nPlayerId),
    CMovableEntity("entity_player", ENT_PLAYER, fX, fY, fZ),
    m_nMovement(0),
    m_chKeyUp(0),
    m_chKeyDown(0),
    m_chKeyLeft(0),
    m_chKeyRight(0),
    m_chKeyDropBomb(0),
    m_pActiveWeapon(nullptr),
    m_bAlive(true),
    m_bDying(false),
    m_pScoreBorder(nullptr),
    m_pScore(nullptr),

    m_MyRepeat(1500.0f, &player::killPlayer, 1)
{
    /**
     * Constructor, ensure that we initialise the entity.
     */
    static bool bRegistered(false);
    CInputDevice* pDevice (
        CInputDeviceManager::getInstance()->getDevice("keyboard1"));
    
    if (pDevice == nullptr)
    {
        throw(std::runtime_error("Cannot find keyboard."));
    }

    setSubID(nPlayerId);

    if (!bRegistered)
    {
        bRegistered = true;
        pDevice->registerEventListener(&player::handleKeyboard);
    }

    CDisplayable *pDisplayable(
        CDisplayableContainer::getInstance()->createDisplayable());
    pDisplayable->setScale(1.50f, 1.50f, 1.50f);
    //pDisplayable->setScale(64.0f, 64.0f, 64.0f);

    int nPlayerIndex(nPlayerId - ENT_PLAYER_1);

    stringstream sstream;
    sstream << "textures/ui/game/player_score_" 
            << (nPlayerIndex + 1) 
            << ".png";
    m_pScoreBorder = new CUISprite(sstream.str());
    CUI::getInstance()->addChild(sstream.str(), m_pScoreBorder);

    m_pScoreBorder->setMarginTop(10.0f);

    m_pScore = (new CUINumber(0));
    m_pScoreBorder->addChild("score", m_pScore);
    m_pScore->setMarginLeft(110);
    m_pScore->setMarginTop(12);

    sstream.str("");
    /*sstream << "textures/ui/game/player_score_" 
        << (nPlayerIndex + 1) 
        << ".png";*/
    sstream << "textures/ui/game/stats_screen_neutral.png";
    m_pStatScreen  = new CUISprite(sstream.str());

    sstream.str("");
    sstream << "textures/ui/game/stats_screen_" 
        << (nPlayerIndex + 1) 
        << ".png";
    CUI::getInstance()->addChild(sstream.str(), m_pStatScreen);
    m_pStatScreen->hide();

    setDisplayable(pDisplayable);
    pDisplayable->setTranslate(fX, fY, fZ);

    pDisplayable->setVisible(true);

    switch(nPlayerId)
    {
    case ENT_PLAYER_1:
        pDisplayable->loadAnimation("models/player/bomberman/bomberman1.x");
        m_pScore->setColour(255, 0, 246);
        m_pScoreBorder->setMarginLeft(100);
        m_pStatScreen->setMarginLeft(300);
        m_pStatScreen->setMarginTop(370);
        break;
    case ENT_PLAYER_2:
        pDisplayable->loadAnimation("models/player/bomberman/bomberman2.x");
        m_pScore->setColour(255, 0, 0);
        m_pScoreBorder->setMarginLeft(952);
        m_pStatScreen->setMarginLeft(800);
        m_pStatScreen->setMarginTop(500);
        break;
    case ENT_PLAYER_3:
        pDisplayable->loadAnimation("models/player/bomberman/bomberman3.x");
        m_pScore->setColour(255, 156, 0);
        m_pScoreBorder->setMarginLeft(739);
        m_pStatScreen->setMarginLeft(800);
        m_pStatScreen->setMarginTop(370);
        break;
    case ENT_PLAYER_4:
        pDisplayable->loadAnimation("models/player/bomberman/bomberman4.x");
        m_pScore->setColour(0, 255, 30);
        m_pScoreBorder->setMarginLeft(313);
        m_pStatScreen->setMarginLeft(300);
        m_pStatScreen->setMarginTop(500);
        break;
    }

    if (getID() == ENT_PLAYER_1)
    {
        m_chKeyUp = static_cast<unsigned char>(Constants::PLAYER_1_UP_KEY);
        m_chKeyDown = static_cast<unsigned char>(Constants::PLAYER_1_DOWN_KEY);
        m_chKeyLeft = static_cast<unsigned char>(Constants::PLAYER_1_LEFT_KEY);
        m_chKeyRight = static_cast<unsigned char>(
            Constants::PLAYER_1_RIGHT_KEY);
        m_chKeyDropBomb = static_cast<unsigned char>(
            Constants::PLAYER_1_BOMB_KEY);
    }
    else if (getID() == ENT_PLAYER_2)
    {
        m_chKeyUp = static_cast<unsigned char>(Constants::PLAYER_2_UP_KEY);
        m_chKeyDown = static_cast<unsigned char>(Constants::PLAYER_2_DOWN_KEY);
        m_chKeyLeft = static_cast<unsigned char>(Constants::PLAYER_2_LEFT_KEY);
        m_chKeyRight = static_cast<unsigned char>(
            Constants::PLAYER_2_RIGHT_KEY);
        m_chKeyDropBomb = static_cast<unsigned char>(
            Constants::PLAYER_2_BOMB_KEY);
    }

    registerStat("kills", 0);
    registerStat("deaths", 0);
    registerStat("bombs placed", 0);
    registerStat("boxes destroyed", 0);
    registerStat("pickups acquired", 0);
    registerStat("of which buffs", 0);
    registerStat("of which debuffs", 0);
}

CPlayer::~CPlayer()
{
    /**
     * Destructor, ensure that all the memory from the dynamic memory is cleared
     * up.
     */
    if (m_arrBuffs.size())
    {
        for (vector<CBuff*>::iterator pBuff(m_arrBuffs.begin());
            pBuff != m_arrBuffs.end(); ++pBuff)
        {
            // Clear buff memory
            (*pBuff)->removeBuff(this);
            delete *pBuff;
        }
        m_arrBuffs.clear();
    }

    if (m_arrWeapons.size())
    {
        for (map<ENTITY_ID, CWeapon*>::const_iterator pWeapon(
             m_arrWeapons.begin()); pWeapon != m_arrWeapons.end(); ++pWeapon)
        {
            // Clear up the memory.
            delete pWeapon->second;
        }
        m_arrWeapons.clear();
    }

    CUI::getInstance()->removeChild(m_pScoreBorder);
    CUI::getInstance()->removeChild(m_pStatScreen);
}

void CPlayer::tick()
{
    /**
     * Executed every tick, ensure we tick the repeat.
     */
    m_MyRepeat.tick();


    int nHeight(0), nWidth(0), nDepth(0);
    CMap *pMap(CMapContainer::getInstance()->getCurrentMap());
    pMap->getTilePositionFromCoord(nHeight, nWidth, nDepth, getX(), getY(), 
        getZ(), 0);
    if (! pMap->isValidTile(nHeight, nWidth, nDepth))
    {
        return;
    }
    CTile *pTile(
        pMap->getTile(nHeight, nWidth, nDepth));
    if (pTile->getEntityCount() > 1)
    {
        vector<CEntity*> arrEntities(pTile->getAllEntitiesInBlock());
        for (vector<CEntity*>::iterator pEntity(arrEntities.begin());
                pEntity != arrEntities.end(); ++pEntity)
        {
            if ((*pEntity)->isCollectible())
            {
                (*pEntity)->collect(this);
            }
        }
    }
}

CBuff *CPlayer::applyBuff(CBuff* pBuff)
{
    /**
     * Dynamically adds a buff to the array of currently stored buffs.
     *
     * @param const ENTITY_ID &nId The ID of the buff to assign.
     * @return CBuff* A pointer of a dynamically generated buff.
     */

    if (pBuff->start(this))
    {

        m_arrBuffs.push_back(pBuff);

        CSound *pSound2;
        updateStatValue("pickups acquired", 1);
        if (pBuff->getBuffState() & BUFF_TYPE_BUFF)
        {
            pSound2 = (CSoundManager::getInstance()->addSound(
                "buff.wav"));
            updateStatValue("of which buffs", 1);
        }
        else
        {
            pSound2 = (CSoundManager::getInstance()->addSound(
                "debuff.ogg"));
            updateStatValue("of which debuffs", 1);
        }

        FMOD::Channel *pNewChannel(nullptr);
        pSound2->createNewChannel(&pNewChannel);
        pNewChannel->setPaused(false);
    }

    return pBuff;
}

void CPlayer::removeBuff(const ENTITY_ID &nBuffID)
{
    /**
     * Removes a buff from that player's buff list, and ensures that the
     * buff's effects are removed from the player.
     *
     * @param cosnt ENTITY_ID &nId The ID of the buff to remove.
     */
    for (vector<CBuff*>::const_iterator pPos(m_arrBuffs.begin()); 
         pPos != m_arrBuffs.end();)
    {
        if ((*pPos)->getId() == nBuffID)
        {
            (*pPos)->removeBuff(this);
            delete (*pPos);
            pPos = m_arrBuffs.erase(pPos);
        }
        else
        {
            ++pPos;
        }
    }
}

void CPlayer::removeBuff(CBuff *pBuff)
{
    /**
     * Removes a buff from that player's buff list, and ensures that the
     * buff's effects are removed from the player.
     *
     * @param cosnt ENTITY_ID &nId The ID of the buff to remove.
     */
    for (vector<CBuff*>::const_iterator pPos(m_arrBuffs.begin()); 
         pPos != m_arrBuffs.end();)
    {
        if ((*pPos) == pBuff)
        {
            (*pPos)->removeBuff(this);
            delete (*pPos);
            pPos = m_arrBuffs.erase(pPos);
            
        }
        else
        {
            ++pPos;
        }
    }
}

bool CPlayer::hasBuff(const ENTITY_ID &nId) const
{
    /**
     * @param const ENTITY_ID &nId THe ID of the buff to test.
     * @return bool Whether the player has the buff with the ID given.
     */
    return getBuff(nId) != nullptr;
}

CBuff *CPlayer::getBuff(const ENTITY_ID &nId) const
{
    /**
     * @param ENTITY_ID &nId The ID of the buff to obtain
     * @return CBuff* A pointer to the buff, nullptr if the id doesn't exist.
     */
    for (vector<CBuff*>::const_iterator pPos(m_arrBuffs.begin()); 
         pPos != m_arrBuffs.end(); ++pPos)
    {
        if ((*pPos)->getId() == nId)
        {
            return (*pPos);
        }
    }
    return nullptr;
}

void CPlayer::setActiveWeapon(CWeapon *pWeapon)
{
    /**
     * Changes the active weapon to a new weapon value.
     *
     * @param CWeapon *pWeapon The new active weapon to change to.
     */
    m_pActiveWeapon = pWeapon;
}

CWeapon *CPlayer::getActiveWeapon() const
{
    /**
     * @return CWeapon* A pointer to the current active weapon.
     */
    return m_pActiveWeapon;
}

CWeapon *CPlayer::addWeapon(CWeapon* pWeapon)
{
    /**
     * Dynamically adds a weapon to the array of currently stored weapons.
     *
     * @param const ENTITY_ID &nId The ID of the weapon to assign.
     * @return CWeapon* A weapon of a dynamically generated weapon.
     */
   
    m_arrWeapons.insert(pair<ENTITY_ID, CWeapon*>(pWeapon->getID(), pWeapon));

    return pWeapon;
}

bool CPlayer::hasWeapon(const ENTITY_ID &nId) const
{
    /**
     * @param const ENTITY_ID &nId The ID of the weapon to check.
     * @return bool Whether or not the player has the current player.
     */
    return m_arrWeapons.find(nId) != m_arrWeapons.end();
}

CWeapon *CPlayer::getWeapon(const ENTITY_ID &nId) const
{
    /**
     * @param const ENTITY_ID &nId The ID of the weapon to obtain
     * @return CWeapon* The weapon to obtain from the ID.
     */
    map<ENTITY_ID, CWeapon*>::const_iterator pWeapon(m_arrWeapons.find(nId));
    if (pWeapon == m_arrWeapons.end())
    {
        return nullptr;
    }
    return pWeapon->second;
}

const D3DXVECTOR3 &CPlayer::getViewVector() const
{
    /**
     * @return const D3DXVECTOR3 The current view vector.
     */
    return m_vecView;
}

void CPlayer::getViewVector(float &fX, float &fY, float &fZ) const
{
    /**
     * Obtains the current view vector in all 3 components.
     *
     * @param float &fX The reference value to hold the X component.
     * @param float &fY The reference value to hold the Y component.
     * @param float &fZ The reference value to hold the Z component.
     */
    fX = m_vecView.x;
    fY = m_vecView.y;
    fZ = m_vecView.z;
}

void CPlayer::setViewVector(const D3DXVECTOR3 &vecView)
{
    /**
     * Sets the view vector of the player to a new value.
     *
     * @param const D3DXVECTOR3 &vecView The new vector to assign.
     */
    m_vecView = vecView;
}

void CPlayer::setViewVector(float fX, float fY, float fZ)
{
    /**
     * Using all 3 components, assing a new view vector of the player.
     *
     * @param float fX The new X component toassign to the view vector.
     * @param float fY The new Y component toassign to the view vector.
     * @param float fZ The new Z component toassign to the view vector.
     */
    m_vecView.x = fX;
    m_vecView.y = fY;
    m_vecView.z = fZ;
}

void CPlayer::setID(const ENTITY_ID &nID)
{
    /**
     * Assigns the player's ID to a new value.
     *
     * @param int nId The new ID of the player.
     */
    m_nID = nID;
}

ENTITY_ID CPlayer::getID() const
{
    /**
     * @return int The player's current ID.
     */
    return m_nID;
}

float CPlayer::getSpeed() const
{
    /**
     * @return float The player's current speed.
     */
    return m_fSpeed;
}

void CPlayer::setSpeed(float fValue)
{
    /**
     * @param float fValue The value to assign to the player's current speed.
     */
    m_fSpeed = fValue;
}

void CPlayer::incrementScore()
{
    /**
     * Increments the player's score value by one.
     */
    ++(*m_pScore);
}

void CPlayer::decrementScore()
{
    /**
     * Decrements the player's score value by one.
     */
    --(*m_pScore);
}

int CPlayer::getScore() const
{
    /**
     * @return int The player's current score.
     */
    return m_pScore->getValue();
}

void CPlayer::setScore(int nScoreValue)
{
    /**
     * Sets the score to a new value.
     *
     * @param int nScoreValue The new value of the score.
     */
    (*m_pScore).setValue(nScoreValue);
}

void CPlayer::updateScore(int nScoreValue)
{
    /**
     * Updates the score by adding to the current value.
     *
     * @param int nScoreValue The amount to add to the current player's score.
     */
    (*m_pScore) += nScoreValue;
}

void CPlayer::walk(int nDirection)
{
    /**
     * Ensures that the player simulates a walk animation in a particular
     * direction. Sets the player's animation state, facing angle and position
     * to the new values given by the direction.
     *
     * @param int nDirection The direction to walk in.
     */
    float fFacingAngle(0.0f);
    int nAnimationSet(1);


    CMapContainer *pMaps(CMapContainer::getInstance());
    if (! pMaps->isMapLoaded())
    {
        return;
    }
    CMap *pMap(pMaps->getCurrentMap());

    if (pMap->isPaused())
    {
        return;
    }

    if (! isAlive() || m_MyRepeat.isRunning())
    {
        return;
    }

    int nCurrentHeight(0);
    int nCurrentWidth(0);
    int nCurrentDepth(0);
    
    pMap->getTilePositionFromCoord(nCurrentHeight, nCurrentWidth, 
                                   nCurrentDepth, getX(), getY(), getZ(), 0);
    
    int nNewHeight(nCurrentHeight);
    int nNewWidth(nCurrentWidth);
    int nNewDepth(nCurrentDepth);

    TBoundingShape bounds(getDisplayable()->getBoundingShape());

    switch (nDirection)
    {
        case PLAYER_DIRECTION_UP:
        {
            nNewDepth += 1;

            CTile *pTile(pMap->getTile(nNewHeight, nNewWidth, nNewDepth));
            if (pTile->isWalkable() && 
                ! pTile->isEntityInBlock(ENT_PROJECTILE_BOMB))
            {
                TBoundingShape tileBounds(
                    pTile->getDisplayable()->getBoundingShape());

                if (bounds.vecMaxBound.x < tileBounds.vecMinBound.x)
                {
                    setX(getX() + getSpeed());
                }
                else if (bounds.vecMinBound.x > tileBounds.vecMaxBound.x)
                {
                    setX(getX() - getSpeed());
                }
                else
                {
                    setZ(getZ() + getSpeed());
                }
            }
            else
            {
                // Do not need to add an offset as the co-ordinate corresponds 
                // to the mid-point
                if (pMap->getTile(
                    nCurrentHeight, nCurrentWidth, nCurrentDepth)->isWalkable())
                {
                    int newTileMidPoint(static_cast<int>(
                        nCurrentDepth * Constants::TILE_SIZE));
                    if(getZ() < newTileMidPoint)
                    {
                        setZ(getZ() + getSpeed());
                    }
                }
            }
            getDisplayable()->setRotate(0.0f, 180.0f, 0.0f);
            break;
        }
        case PLAYER_DIRECTION_DOWN:
        {
            nNewDepth -= 1;

            CTile *pTile(pMap->getTile(nNewHeight, nNewWidth, nNewDepth));
            if (pTile->isWalkable() && 
                ! pTile->isEntityInBlock(ENT_PROJECTILE_BOMB))
            {
                
                TBoundingShape tileBounds(
                    pTile->getDisplayable()->getBoundingShape());

                if (bounds.vecMinBound.x < tileBounds.vecMinBound.x)
                {
                    setX(getX() + getSpeed());
                }
                else if (bounds.vecMaxBound.x > tileBounds.vecMaxBound.x)
                {
                    setX(getX() - getSpeed());
                }
                else
                {
                    setZ(getZ() - getSpeed());
                }                
            }
            else
            {
                // Do not need to add an offset as the co-ordinate corresponds 
                // to the mid-point
                if (pMap->getTile(
                    nCurrentHeight, nCurrentWidth, nCurrentDepth)->isWalkable())
                {
                    int newTileMidPoint(static_cast<int>(
                        nCurrentDepth * Constants::TILE_SIZE));
                    if(getZ() > newTileMidPoint)
                    {
                        setZ(getZ() - getSpeed());
                    }
                }
            }
            getDisplayable()->setRotate(0.0f, 0.0f, 0.0f);
            break;
        }
        case PLAYER_DIRECTION_LEFT:
        {
            nNewWidth -= 1;

            CTile *pTile(pMap->getTile(nNewHeight, nNewWidth, nNewDepth));
            if (pTile->isWalkable() && 
                ! pTile->isEntityInBlock(ENT_PROJECTILE_BOMB))
            {
                
                TBoundingShape tileBounds(
                    pTile->getDisplayable()->getBoundingShape());

                if (bounds.vecMaxBound.z < tileBounds.vecMinBound.z)
                {
                    setZ(getZ() + getSpeed());
                }
                else if (bounds.vecMinBound.z > tileBounds.vecMaxBound.z)
                {
                    setZ(getZ() - getSpeed());
                }
                else
                {
                    setX(getX() - getSpeed());
                }                
            }
            else
            {
                // Do not need to add an offset as the co-ordinate corresponds 
                // to the mid-point
                if (pMap->getTile(
                    nCurrentHeight, nCurrentWidth, nCurrentDepth)->isWalkable())
                {
                    int newTileMidPoint(static_cast<int>(
                        nCurrentWidth * Constants::TILE_SIZE));
                    if(getX() > newTileMidPoint)
                    {
                        setX(getX() - getSpeed());
                    }
                }
            }
            getDisplayable()->setRotate(0.0f, 90.0f, 0.0f);
            break;
        }
        case PLAYER_DIRECTION_RIGHT:
        {
            nNewWidth += 1;

            CTile *pTile(pMap->getTile(nNewHeight, nNewWidth, nNewDepth));
            if (pTile->isWalkable() && 
                ! pTile->isEntityInBlock(ENT_PROJECTILE_BOMB))
            {
                
                TBoundingShape tileBounds(
                    pTile->getDisplayable()->getBoundingShape());

                if (bounds.vecMinBound.z < tileBounds.vecMinBound.z)
                {
                    setZ(getZ() + getSpeed());
                }
                else if (bounds.vecMaxBound.z > tileBounds.vecMaxBound.z)
                {
                    setZ(getZ() - getSpeed());
                }
                else
                {
                    setX(getX() + getSpeed());
                }                
            }
            else
            {
                // Do not need to add an offset as the co-ordinate corresponds 
                // to the mid-point
                if (pMap->getTile(
                    nCurrentHeight, nCurrentWidth, nCurrentDepth)->isWalkable())
                {
                    int newTileMidPoint(
                        static_cast<int>(nCurrentWidth * Constants::TILE_SIZE));
                    if(getX() < newTileMidPoint)
                    {
                        setX(getX() + getSpeed());
                    }
                }
            }
            getDisplayable()->setRotate(0.0f, 270.0f, 0.0f);
            break;
        }
        default:
        {
            nAnimationSet = 0;
        }
    }

    pMap->getTilePositionFromCoord(nNewHeight, nNewWidth, 
                                   nNewDepth, getX(), getY(), getZ(), 0);
    if (! (nNewHeight == nCurrentHeight &&
           nNewWidth == nCurrentWidth &&
           nNewDepth == nCurrentDepth))
    {
        if (pMap->isValidTile(nCurrentHeight, nCurrentWidth, nCurrentDepth))
        {
            CTile *pTile(
                pMap->getTile(nCurrentHeight, nCurrentWidth, nCurrentDepth));
            pTile->removeEntityFromBlock(this);
        }

        if (pMap->isValidTile(nNewHeight, nNewWidth, nNewDepth))
        {
            CTile *pTile(
                pMap->getTile(nNewHeight, nNewWidth, nNewDepth));
            pTile->addEntityInBlock(this);
        }
    }

    getDisplayable()->setAnimationSet(nAnimationSet);
    //getDisplayable()->setAnimationSet(3);
}

void CPlayer::handleKeyPress(const DEVICE_EVENT &params) 
{
    /**
     * Handles a key press from the keyboard. Try and work out which direction
     * the player should be walking in and move the player.
     *
     * @param const DEVICE_EVENT &params The event information passed from the
     *    device handler.
     */

    if (! isAlive()) return;

    if (params.type != DEVICE_KEYBOARD) return;

    CKeyboard* pKeyboard(dynamic_cast<CKeyboard*>(params.pDevice));


    // Walk up
    if (pKeyboard->keyPressed(params.charrKeyboardData, m_chKeyUp))
    {
        if (! isDirectionInStack(PLAYER_DIRECTION_UP))
        {
            m_nMovement = (m_nMovement << 8) + PLAYER_DIRECTION_UP;
        }
    }
    else
    {
        // Key is no long pressed
        if (isDirectionInStack(PLAYER_DIRECTION_UP))
        {
            removeFromStack(PLAYER_DIRECTION_UP);
        }
    }

    // Walk down
    if (pKeyboard->keyPressed(params.charrKeyboardData, m_chKeyDown ))
    {
        if (! isDirectionInStack(PLAYER_DIRECTION_DOWN))
        {
            m_nMovement = (m_nMovement << 8) + PLAYER_DIRECTION_DOWN;
        }
    }
    else
    {
        // Key is no long pressed
        if (isDirectionInStack(PLAYER_DIRECTION_DOWN))
        {
            removeFromStack(PLAYER_DIRECTION_DOWN);
        }
    }

    // Walk left
    if (pKeyboard->keyPressed(params.charrKeyboardData, m_chKeyLeft))
    {
        if (!isDirectionInStack(PLAYER_DIRECTION_LEFT))
        {
            m_nMovement = (m_nMovement << 8) + PLAYER_DIRECTION_LEFT;
        }
    } 
    else
    {
        // Key is no long pressed
        if (isDirectionInStack(PLAYER_DIRECTION_LEFT))
        {
            removeFromStack(PLAYER_DIRECTION_LEFT);
        }
    }
    
    // Walk right
    if (pKeyboard->keyPressed(params.charrKeyboardData, m_chKeyRight))
    {
        if (!isDirectionInStack(PLAYER_DIRECTION_RIGHT))
        {
            m_nMovement = (m_nMovement << 8) + PLAYER_DIRECTION_RIGHT;
        }
    }
    else
    {
        // Key is no long pressed
        if (isDirectionInStack(PLAYER_DIRECTION_RIGHT))
        {
            removeFromStack(PLAYER_DIRECTION_RIGHT);
        }
    }

    if (pKeyboard->keyPressed(params.charrKeyboardData, DIK_SPACE) && getID() 
         == ENT_PLAYER_1)
    {
        pKeyboard->registerKeyDebounce(DIK_SPACE, CTimer() + 0.5f);

        CMap *pMap(CMapContainer::getInstance()->getCurrentMap());

        for (int i(0); i < pMap->getHeight(); ++i)
        {
            for (int j(0); j < pMap->getWidth(); ++j)
            {
                for (int k(0); k < pMap->getDepth(); ++k)
                {
                    CTile *pTile (pMap->getTile(i, j, k));
                    if (pTile->isDestructible())
                    {
                        pTile->destroy();
                    }
                }
            }
        }
    }

    if (pKeyboard->keyPressed(params.charrKeyboardData, DIK_F1) && getID() 
         == ENT_PLAYER_1)
    {
        pKeyboard->registerKeyDebounce(DIK_F1, CTimer() + 500.0f);

        CDeviceManager *pDevice(CDeviceManager::getInstance());
        pDevice->setCheatMode(pDevice->getCheatMode() ^ Constants::GODMODE);
    }

    if (pKeyboard->keyPressed(params.charrKeyboardData, DIK_F2) && getID() 
         == ENT_PLAYER_1)
    {
        pKeyboard->registerKeyDebounce(DIK_F2, CTimer() + 500.0f);

        CDeviceManager *pDevice(CDeviceManager::getInstance());
        pDevice->setCheatMode(pDevice->getCheatMode() ^ Constants::OCCUPIED_BOUNDING_BOXES);
    }

    if (pKeyboard->keyPressed(params.charrKeyboardData, DIK_F3) && getID() 
         == ENT_PLAYER_1)
    {
        pKeyboard->registerKeyDebounce(DIK_F3, CTimer() + 500.0f);

        CDeviceManager *pDevice(CDeviceManager::getInstance());
        pDevice->setCheatMode(pDevice->getCheatMode() ^ Constants::BOUNDING_BOXES);
    }

    if (pKeyboard->keyPressed(params.charrKeyboardData, m_chKeyDropBomb))
    {
        CMapContainer *pMaps(CMapContainer::getInstance());
        if (! pMaps->isMapLoaded())
        {
            return;
        }
        CMap *pMap(pMaps->getCurrentMap());

        if (! pMap->isPaused())
        {
            pKeyboard->registerKeyDebounce(m_chKeyDropBomb, CTimer() + 0.5f);

            CWeapon *pWeapon(getActiveWeapon());

            if (pWeapon != nullptr)
            {
                pWeapon->fire();
            }
        }
    }

    if (pKeyboard->keyPressed(params.charrKeyboardData, DIK_ESCAPE) && getID() 
         == ENT_PLAYER_1)
    {
        pKeyboard->registerKeyDebounce(DIK_ESCAPE, CTimer() + 0.5f);

        CMap *pMap(CMapContainer::getInstance()->getCurrentMap());

        pMap->pause();
    }

    // Initialise the walk movement
    walk(m_nMovement & 0xFF);
}

void CPlayer::onCreate(CMap *pMap)
{
    /**
     * Executed when the player entity is created. Add the play to the current
     * tile that he is on.
     */
    int nHeight(0), nWidth(0), nDepth(0);
    if (pMap != nullptr)
    {
        pMap->getTilePositionFromCoord(nHeight, nWidth, nDepth,
            getX(), getY(), getZ(), 0);
        if (pMap->isValidTile(nHeight, nWidth, nDepth))
        {
            CTile *pTile(pMap->getTile(nHeight, nWidth, nDepth));
            pTile->addEntityInBlock(this);
        }

        if (pMap->isDemo())
        {
            m_pScoreBorder->hide();
            m_pScore->hide();
        }
    }
}

void CPlayer::setAliveStatus(bool bAlive)
{
    m_bAlive = bAlive;
}

void CPlayer::reset()
{
    /**
     * Reset the player, stop any repeats.
     */
    if (m_arrBuffs.size())
    {
        for (vector<CBuff*>::iterator pBuff(m_arrBuffs.begin()); 
             pBuff != m_arrBuffs.end(); ++pBuff)
        {
            (*pBuff)->removeBuff(this);
            delete (*pBuff);
        }
        m_arrBuffs.clear();
    }

    getDisplayable()->setAnimationSet(0);

    hideStats();

    m_MyRepeat.stop();
    m_MyRepeat.clearParams();
    m_MyRepeat.setRepeatCount(1);
}

bool CPlayer::isDirectionInStack(DIRECTION_ID direction)
{
    /**
     * Tests to see if a the user is currently pressing the key to walk in a 
     * given direction.
     *
     * @param DIRECTION_ID direction The Direction to see if the player is 
     *    walking in
     * @return bool Whether or not the direction is in the stack of movements
     */
#define getByte(mask, position) ((m_nMovement & mask) >> position)

            // 00 00 00 FF
    return (getByte(0xFF,0) == direction || 
            // 00 00 FF 00
            getByte(0xFF00, 8) == direction || 
            // 00 FF 00 00
            getByte(0xFF0000, 16) == direction ||
            // FF 00 00 00 
            getByte(0xFF000000, 24) == direction);    
#undef getByte
}

void CPlayer::removeFromStack(DIRECTION_ID direction)
{
    /**
     * Stop the player from walking in a given direction. This updates the
     * stack so the key is no longer being recognised as being pressed.
     *
     * @param DIRECTION_ID direction The direction to remove from the history
     */
#define isInPosition(mask, position) ((m_nMovement & mask)  >> position) \
    == direction

    if (isDirectionInStack(direction))
    {
        if (isInPosition(0xFF, 0))
        {
            m_nMovement = (m_nMovement ^ (m_nMovement & 0xFF));
            m_nMovement = m_nMovement >> 8;
        }
        else if (isInPosition(0xFF00, 8))
        {
            m_nMovement = (m_nMovement ^ (m_nMovement & 0xFF00));
            int nValue(m_nMovement & 0xFFFF0000);
            m_nMovement -= nValue;
            m_nMovement += nValue >> 8;
        }
        else if (isInPosition(0xFF0000, 16))
        {
            m_nMovement = (m_nMovement ^ (m_nMovement & 0xFF0000));
            int nValue(m_nMovement & 0xFF000000);
            m_nMovement -= nValue;
            m_nMovement += nValue >> 8;
        }
        else if (isInPosition(0xFF000000, 24))
        {
            m_nMovement = (m_nMovement ^ (m_nMovement & 0xFF000000));
        }
    }
#undef isInPosition
}

bool CPlayer::isAlive() const
{
    /**
     * @return bool Whether or not the player is alive
     */
    return m_bAlive;
}

bool CPlayer::isDying() const
{
    return m_bDying;
}

void CPlayer::kill()
{
    /**
     * Kill the player and change their alive status to false.
     */
    if (!isDying() && isAlive())
    {
        m_bDying = true;

        getDisplayable()->setAnimationSet(2);
        m_MyRepeat.registerParam(this, utils::TYPE_OTHER, sizeof(*this));
        m_MyRepeat.start();

        int nHeight(0), nWidth(0), nDepth(0);

        // Remove the entity from the block
        CMap *pMap(CMapContainer::getInstance()->getCurrentMap());
        if (pMap != nullptr)
        {
            pMap->getTilePositionFromCoord(nHeight, nWidth, nDepth,
                getX(), getY(), getZ(), 0);
            if (pMap->isValidTile(nHeight, nWidth, nDepth))
            {
                CTile *pTile(pMap->getTile(nHeight, nWidth, nDepth));

                pTile->removeEntityFromBlock(this);
            }
        }

        CSound *pSound = (CSoundManager::getInstance()->addSound(
            "death.wav"));
        FMOD::Channel *pNewChannel(nullptr);
        pSound->createNewChannel(&pNewChannel);
        pNewChannel->setPaused(false);

        updateStatValue("deaths", 1);
    }
}

void CPlayer::respawn()
{
    /**
     * Respawns a player (simply sets their alive status to true)
     */
    if (! isAlive())
    {
        m_bAlive = true;
        m_bDying = false;
    }
}

void CPlayer::registerStat(const string &szStatName, signed long nInitialValue)
{
    /**
     * Registers a stat to be printed on the stat screen at the end of each
     * round.
     */
    CPanel *pChild(m_pStatScreen->getChild(szStatName));
    if (pChild != nullptr)
    {
        // Stat already exists
        return;
    }
    CUINumber *pNumber(new CUINumber(nInitialValue));
    pNumber->setFont(CFontManager::getInstance()->getFont("bombermantiny"));
    pNumber->setMarginLeft(190);
    pNumber->setMarginTop(m_pStatScreen->getChildrenCount() / 2 * 12.0f +
       (m_pStatScreen->getChildrenCount() % 2 ? 12 : 0) + 15);

    CUIText *pText(new CUIText(szStatName, "bombermantiny"));
    pText->setFont(CFontManager::getInstance()->getFont("bombermantiny"));
    pText->setMarginLeft(15);
    pText->setMarginTop(m_pStatScreen->getChildrenCount() / 2 * 12.0f +
        (m_pStatScreen->getChildrenCount() % 2 ? 12 : 0) + 15);

    switch(getSubID())
    {
    case ENT_PLAYER_1:
        pText->setColour(255, 0, 246, 180);
        pNumber->setColour(255, 0, 246, 180);
        break;
    case ENT_PLAYER_2:
        pText->setColour(255, 0, 0, 180);
        pNumber->setColour(255, 0, 0, 180);
        break;
    case ENT_PLAYER_3:
        pText->setColour(130, 80, 0, 180);
        pNumber->setColour(130, 80, 0, 180);
        break;
    case ENT_PLAYER_4:
        pText->setColour(12, 139, 0, 180);
        pNumber->setColour(12, 139, 0, 180);
        break;
    }

    pNumber->hide();
    pText->hide();

    m_pStatScreen->addChild(szStatName, pNumber);
    m_pStatScreen->addChild(szStatName + "_title", pText);
}

signed long CPlayer::getStatValue(const string &szStatName) const
{
    /**
     * @param szStatName The name of the stat to obtain the value for
     * @return signed long The value of the stat
     */
    CPanel *pChild(m_pStatScreen->getChild(szStatName));
    if (pChild != nullptr)
    {
        // Stat exists
        return reinterpret_cast<CUINumber*>(pChild)->getValue();
    }
    return 0;
}

bool CPlayer::updateStatValue(const string &szStatName, long signed nAmount)
{
    /**
     * Increments the stat value by a given amount.
     *
     * @param const string &szStatName The name of the stat to increase.
     * @param long signed nAmount The amount to update by
     * @return bool Whether or not the stat could be updated (found)
     */
    CPanel *pChild(m_pStatScreen->getChild(szStatName));
    if (pChild != nullptr)
    {
        // Stat exists
        reinterpret_cast<CUINumber*>(pChild)->updateValue(nAmount);
        return true;
    }
    return false;
}

bool CPlayer::setStatValue(const string &szStatName, signed long nValue)
{
    /**
     * Sets the stat value to a given amount.
     *
     * @param const string &szStatName The name of the stat to set.
     * @param long signed nAmount The amount to set
     * @return bool Whether or not the stat could be set (found)
     */
    CPanel *pChild(m_pStatScreen->getChild(szStatName));
    if (pChild != nullptr)
    {
        // Stat exists
        reinterpret_cast<CUINumber*>(pChild)->setValue(nValue);
        return true;
    }
    return false;
}

void CPlayer::showStats()
{
    /**
     * Show the player's stats
     */
    m_pStatScreen->show();
    
    map<string, CPanel*> arrChildren(m_pStatScreen->getChildren());
    for (map<string, CPanel*>::const_iterator pPanel(arrChildren.begin());
         pPanel != arrChildren.end(); ++pPanel)
    {
        pPanel->second->show();
    }
}

void CPlayer::hideStats()
{
    /**
     * Hide the player's stats
     */
    m_pStatScreen->hide();

    map<string, CPanel*> arrChildren(m_pStatScreen->getChildren());
    for (map<string, CPanel*>::const_iterator pPanel(arrChildren.begin());
        pPanel != arrChildren.end(); ++pPanel)
    {
        pPanel->second->hide();
    }
}

namespace player
{
    void handleKeyboard(const DEVICE_EVENT &params)
    {
        /** 
         * Executed automatically when a key on the keyboard has been pressed.
         * This is a callback from the keyboard handler.
         *
         * @param const DEVICE_EVENT &params The event information.
         */
        if (params.type == DEVICE_KEYBOARD)
        {
            // We're working on a keyboard device, this should always be true

            // Get the entity list for players
            vector<CEntity*> arrPlayers(
                CEntityContainer::getInstance()->getEntityList(ENT_PLAYER));

            int nPos(0);

            for (vector<CEntity*>::iterator 
                pEntity(arrPlayers.begin());
                pEntity != arrPlayers.end(); ++pEntity, ++nPos)
            {
                CPlayer *pPlayer(dynamic_cast<CPlayer*>(*pEntity));               
                pPlayer->handleKeyPress(params);
            }
        }
    }

    void killPlayer(DELAY_PARAMS &params)
    {
        /**
         * Kills a specific player.
         *
         * @param DELAY_PARAMS params the parameters
         */
        CPlayer *pPlayer(GET_POINTER_FROM_PARAM(params, 0, CPlayer));
        pPlayer->setVisible(false);
        pPlayer->setAliveStatus(false);

    }
} // namespace player